﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NetJavap.AttributeInfos;
using NetJavap.ConstantInfos;

namespace NetJavap.JVM
{
    public class JVMCodeRunner
    {
        private Stack<JVMStackFrame> _stackFrameStack;
        private UInt32 _pc;
        private JOpcode _currentOpcode;

        public UInt32 PC { get { return _pc; } }
        public CodeAttributeInfo CurrentCodeInfo { get { return this.CurrentStackFrame.CodeInfo; } }
        public Stack<JVMStackFrame> StackFrameStack { get { return this._stackFrameStack; } }
        public JVMStackFrame CurrentStackFrame { get { return this._stackFrameStack.Peek(); } }
        public JOpcode CurrentOpcode { get { return this._currentOpcode; } }

        public static event EventHandler<NativeInvokeEventArgs> OnNativeInvokeNeed;

        public bool ExecuteSetp()
        {
            if (StackFrameStack.Count == 0)
            {
                return true;
            }
            _currentOpcode = this.CurrentCodeInfo.Opcodes[this._pc];
            _currentOpcode.ExecuteAction(this);
            if (StackFrameStack.Count == 0)
            {
                return true;
            }
            else
            {
                return PC >= CurrentCodeInfo.ByteCode.Length;
            }
        }

        public JVMCodeRunner(CodeAttributeInfo codeInfo)
        {
            this._stackFrameStack = new Stack<JVMStackFrame>();
            this._stackFrameStack.Push(new JVMStackFrame(this, codeInfo));
        }
        public JVMCodeRunner CheckCast(UInt16 consId)
        {
            Object obj = this.PopOperateStackVar();
            ConstantInfo info = this.CurrentCodeInfo.ParentClass.ConstantPool[consId];
            if (info is ConstantClassInfo)
            {
                if (!(obj is JavaObject))
                {
                    throw new InvalidCastException();
                }
                ConstantClassInfo cinfo = info as ConstantClassInfo;
                JavaObject jobj = obj as JavaObject;
                if (cinfo.Class != jobj.Class)
                {
                    if (jobj.Class is ArrayJavaClass)
                    {
                        ArrayJavaClass acls = jobj.Class as ArrayJavaClass;
                        if (acls.Type != cinfo.Class)
                        {
                            throw new InvalidCastException();
                        }
                    }
                }
            }
            else
            {
                Object infoVal = info.GetValue();
                if (typeof(Object) != infoVal.GetType())
                {
                    throw new InvalidCastException();
                }
            }
            this.PushVarToOperateStack<Object>(obj);

            return this;
        }

        public JVMCodeRunner InstanceOf(UInt16 consId)
        {
            ConstantClassInfo clsInfo = this.CurrentCodeInfo.ParentClass.ConstantPool[consId] as ConstantClassInfo;
            JavaObject obj = this.PopOperateStackVar<JavaObject>();
            if (obj.Class == clsInfo.Class)
            {
                this.PushVarToOperateStack<Int32>(1);
            }
            else
            {
                this.PushVarToOperateStack<Int32>(0);
            }
            return this;
        }

        public JVMCodeRunner PushVarToOperateStack<T>(T val)
        {
            this.CurrentStackFrame.OperateStack.Push(val);
            return this;
        }

        public JVMCodeRunner StoreToVarTable<T>(UInt16 index)
        {
            Object obj = this.CurrentStackFrame.OperateStack.Pop();
            if (obj is Char && typeof(T) == typeof(Int32))
            {
                obj = (Int32)(Char)obj;
            }
            this.CurrentStackFrame.LocalVariableTable[index]
                = (T)obj;
            return this;
        }

        public JVMCodeRunner LoadToOperateStack<T>(UInt16 index)
        {
            var obj = this.CurrentStackFrame.LocalVariableTable[index];
            this.CurrentStackFrame.OperateStack.Push((T)obj);
            return this;
        }

        public JVMCodeRunner IncrementLocalVar(Byte index, SByte val)
        {
            Object var = this.CurrentStackFrame.LocalVariableTable[index];
            if (!(var is Int32))
            {
                throw new InvalidOperationException("递增值必须为Int32类型！");
            }
            var = (int)var + val;
            this.CurrentStackFrame.LocalVariableTable[index] = var;
            return this;
        }

        public JVMCodeRunner BinaryOperator<T>(Func<T, T, T> oper)
        {
            return this.BinaryOperator<T, T, T>(oper);
        }

        public JVMCodeRunner BinaryOperator<TOprand, TValue>(Func<TOprand, TOprand, TValue> oper)
        {
            return this.BinaryOperator<TOprand, TOprand, TValue>(oper);
        }

        public JVMCodeRunner BinaryOperator<TLeft, TRight, TValue>(Func<TLeft, TRight, TValue> oper)
        {
            Object left, right;
            right = this.CurrentStackFrame.OperateStack.Pop();
            left = this.CurrentStackFrame.OperateStack.Pop();
            this.CurrentStackFrame.OperateStack.Push((TValue)oper((TLeft)left, (TRight)right));
            return this;
        }

        public JVMCodeRunner SingleOperator<T>(Func<T, T> oper)
        {
            Object num = this.CurrentStackFrame.OperateStack.Pop();
            this.CurrentStackFrame.OperateStack.Push(oper((T)num));
            return this;
        }

        public JVMCodeRunner ConvertOperateStackTop<TBefore, TAfter>(Func<TBefore, TAfter> converter)
        {
            Object before = this.CurrentStackFrame.OperateStack.Pop();
            TAfter after = converter((TBefore)before);
            this.CurrentStackFrame.OperateStack.Push(after);
            return this;
        }

        public JVMCodeRunner Invoke(UInt16 methodRefId, bool isStatic)
        {
            JavaClass currentCls = this.CurrentStackFrame.CodeInfo.ParentClass;
            ConstantMethodRefInfo mrefInfo = currentCls.ConstantPool[methodRefId] as ConstantMethodRefInfo;
            var v = mrefInfo.ReturnType;
            MethodInfo minfo = mrefInfo.TargetMethod;
            JavaObject instance = null;
            if (!minfo.AccessFlags.HasFlag(AccessFlags.Native))
            {
                CodeAttributeInfo codeInfo = minfo.GetAttribute<CodeAttributeInfo>();
                LocalVariableTableAttributeInfo localVar = null;
                if (codeInfo != null)
                {
                    localVar = codeInfo.GetAttribute<LocalVariableTableAttributeInfo>();
                }
                JVMStackFrame newFrame = new JVMStackFrame(this, codeInfo);
                for (UInt16 i = 1; i <= minfo.Parameters.Length; i++)
                {
                    int varIndex = minfo.Parameters.Length - i;
                    if (!isStatic)
                    {
                        varIndex += 1;
                    }
                    newFrame.LocalVariableTable[
                        localVar == null ? (UInt16)(varIndex) :
                        localVar.LocalVariables[varIndex].Index
                        ] = CurrentStackFrame.OperateStack.Pop();
                }
                if (!isStatic)
                {
                    instance = this.PopOperateStackVar<JavaObject>();
                    newFrame.LocalVariableTable[0] = instance;
                    newFrame.BindInstance = instance;
                }

                //受控代码截断，支持对核心库的拓展
                ManagedJavaClass manageredCls = minfo.ParentClass as ManagedJavaClass;
                Type type = minfo.ParentClass.GetType();
                if (manageredCls != null)
                {
                    Object res = manageredCls.Invoke(minfo, newFrame);
                    //当前调用存在返回值
                    if (minfo.ReturnType.Name != "V")
                    {
                        //返回值压栈
                        this.PushVarToOperateStack(res);
                    }
                    //PC增加
                    this.IncrementPC();
                }
                else
                {
                    this.StackFrameStack.Push(newFrame);
                    this._pc = 0;
                }
            }
            else
            {
                if (!isStatic)
                {
                    instance = this.PopOperateStackVar<JavaObject>();
                }
                Dictionary<UInt16, Object> paramTable = new Dictionary<UInt16, Object>();
                for (UInt16 i = 1; i <= minfo.Parameters.Length; i++)
                {
                    paramTable[(UInt16)(minfo.Parameters.Length - i)]
                        = CurrentStackFrame.OperateStack.Pop();
                }
                if (OnNativeInvokeNeed != null)
                {
                    var invokeContext = new NativeInvokeEventArgs(paramTable, minfo, instance);
                    OnNativeInvokeNeed(this, invokeContext);
                    this.CurrentStackFrame.OperateStack.Push(invokeContext.ReturnValue);
                    this.IncrementPC();
                }
                else
                {
                    throw new InvalidOperationException("未绑定本地方法调用处理函数，本地调用失败！");
                }
            }
            return this;
        }

        public JVMCodeRunner IncrementPC()
        {
            this._pc += (UInt32)this.CurrentOpcode.OtherBytes.Length + 1;
            return this;
        }

        public JVMCodeRunner Dup()
        {
            this.CurrentStackFrame.OperateStack.Push(
                this.CurrentStackFrame.OperateStack.Peek());
            return this;
        }

        public JVMCodeRunner Goto(Int32 pos, Func<bool> condition = null)
        {
            if (condition == null || condition())
            {
                this._pc = (UInt32)(this._pc + pos);
            }
            return this;
        }

        public Object GetConstantValue(UInt16 index)
        {
            return this.CurrentCodeInfo.ParentClass.ConstantPool[index].GetValue();
        }

        public JVMCodeRunner NewClassInstance(UInt16 clsId)
        {
            ConstantClassInfo clsInfo = this.CurrentCodeInfo.ParentClass.ConstantPool[clsId] as ConstantClassInfo;
            JavaClass cls = JVMClassLoader.LoadClass(clsInfo.TypeInfo.Name);
            JavaObject obj = new JavaObject(cls);
            this.PushVarToOperateStack<JavaObject>(obj);
            return this;
        }

        public JVMCodeRunner NewArrayInstance(Object type, Int32 length)
        {
            ArrayJavaClass arrayCls = ArrayJavaClass.GetArrayClass(type, length);
            ArrayJavaObject obj = new ArrayJavaObject(arrayCls);
            this.PushVarToOperateStack<ArrayJavaObject>(obj);
            return this;
        }

        public JVMCodeRunner NewClassArrayInstance(UInt16 clsId, Int32 length)
        {
            ConstantClassInfo clsInfo = this.CurrentCodeInfo.ParentClass.ConstantPool[clsId] as ConstantClassInfo;
            JavaClass cls = JVMClassLoader.LoadClass(clsInfo.TypeInfo.Name);
            return this.NewArrayInstance(cls, length);
        }

        public JVMCodeRunner ArrayLength(ArrayJavaObject arrIns)
        {
            ArrayJavaClass arrCls = arrIns.Class as ArrayJavaClass;
            this.PushVarToOperateStack<Int32>(arrCls.Length);
            return this;
        }

        public JVMCodeRunner StoreArrayValue<T>(T val, Int32 index, ArrayJavaObject arrIns)
        {
            arrIns.Elements[index] = val;
            return this;
        }

        public JVMCodeRunner LoadArrayValue<T>(Int32 index, ArrayJavaObject arrIns)
        {
            this.PushVarToOperateStack<T>((T)arrIns.Elements[index]);
            return this;
        }

        public JVMCodeRunner PutField(UInt16 fieldId, Object val)
        {
            JavaObject ins = this.CurrentStackFrame.BindInstance;
            ins.FieldValues[fieldId] = val;
            return this;
        }

        public JVMCodeRunner GetField(UInt16 fieldId)
        {
            JavaObject ins = this.CurrentStackFrame.BindInstance;
            return PushVarToOperateStack(ins.FieldValues[fieldId]);
        }

        public T PopOperateStackVar<T>()
        {
            Object obj = PopOperateStackVar();
            return (T)obj;
        }

        public Object PopOperateStackVar()
        {
            return this.CurrentStackFrame.OperateStack.Pop();
        }

        public JVMCodeRunner Return()
        {
            if (this._stackFrameStack.Count > 0)
            {
                this._pc = this.CurrentStackFrame.ReturnAddress;
                this._stackFrameStack.Pop();
            }
            else
            {
                this._pc = (UInt16)(this.CurrentCodeInfo.ByteCode.Length + 1);
            }
            return this;
        }

        public JVMCodeRunner GetStaticValue(UInt16 id)
        {
            return this.PushVarToOperateStack(this.CurrentOpcode.ParentClass.ConstantPool[id].GetValue());
        }

        public JVMCodeRunner SetStaticValue(UInt16 id, Object value)
        {
            this.CurrentOpcode.ParentClass.ConstantPool[id].SetValue(value);
            return this;
        }

        public JVMCodeRunner Return<T>()
        {
            if (this._stackFrameStack.Count >= 0)
            {
                this._pc = this.CurrentStackFrame.ReturnAddress;
                JVMStackFrame prevFrame = this._stackFrameStack.Pop();
                Object returnVal = prevFrame.OperateStack.Pop();
                this.CurrentStackFrame.OperateStack.Push((T)returnVal);
            }
            return this;
        }

    }
}
